﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Drawing;
using System.Drawing.Drawing2D;
using Petra.Gis.Geometry;
using Petra.Gis.Geometry.Support;
using Petra.Gis.Geometry.Utils;
using Petra.Gis.Drawing;
using Petra.Gis.RenderingServices.PlanarBase ;


namespace Petra.Gis.Drawing.GDIPlus
{

  public partial class SurfaceGridRenderer : GridRenderer
  {

    //important !!! this is array of output points for rendered geometries
    //should work as heap
    //GC should leave this variable untouched (same size) during procedure calls (and resize)
    //initial size is 100 000 pts, should be enough for typical geometry
    private System.Drawing.Point[] _outPtsCache = new System.Drawing.Point[100000];

    protected Pen _outpen;
    protected SolidBrush _outFillBrush;
    private int _lastPointIndex;
    private object _renderTo;


    public SurfaceGridRenderer(MemGridMap map, Graphics g, DisplayTransformMatrix m, RenderStyle s)
      :base(map, g, m, s)
    {

      //parse style
      _outpen = new Pen(Color.FromArgb((int)_style["Color"]), (int)_style["LineWidth"]);
      _outFillBrush = new SolidBrush(Color.FromArgb((int)_style["Color"]));
    }




    public override DomainGeometryType RendererGeometryDomain
    {
      get { return DomainGeometryType.Surface; }
    }



    //todo:update...
    public override bool IsRenderable(GeometryBase geometry)
    {
      return true ;
    }




    public override void RenderGeometry(GeometryBase geometry)
    {
      if (geometry is LineRing)
      {
        _renderTo = _outGraphic;
        renderRing((LineRing)geometry);
      }
      else if (geometry is PathRing)
      {
        _renderTo = _outGraphic;
        renderPathRing((PathRing)geometry);
      }
      else if (geometry is Polygon)
      {
        if (((Polygon)geometry).Count == 1)
          RenderGeometry(((Polygon)geometry)[0]);
        else
          renderPolygon((Polygon)geometry);
      }
      else if (geometry is MultiArea)
        foreach (GeometryBase g in (MultiArea)geometry)
          RenderGeometry(g);
      else if (geometry is GeometryCollection)
        foreach (GeometryBase g in (GeometryCollection)geometry)
          RenderGeometry(g);

    }



    //draws LineRing
    private void renderRing(LineRing geom)
    {

      _lastPointIndex = 0;

      addPointsToOpenGraphicsPath(geom.coordList.getBuffer(),geom.coordList.position, geom.coordList.Count, geom.coordList.dimension);

      //if something is ready to output...
      if (_lastPointIndex > 0)
      {
        if (_outPtsCache.Length > _lastPointIndex + 1)
          Array.Resize<System.Drawing.Point>(ref _outPtsCache, _lastPointIndex + 1);

        _outGraphic.FillPolygon(_outFillBrush, _outPtsCache);
        _outGraphic.DrawPolygon(_outpen, _outPtsCache);
      }
      
    }



    private System.Drawing.Point[] prepareLineRing(LineRing g)
    {

      _lastPointIndex = 0;

      addPointsToOpenGraphicsPath(g.coordList.getBuffer(), g.coordList.position, g.coordList.Count, g.coordList.dimension);

      //if something is ready to output...
      if (_lastPointIndex > 0)
      {
        if (_outPtsCache.Length > _lastPointIndex + 1)
          Array.Resize<System.Drawing.Point>(ref _outPtsCache, _lastPointIndex + 1);

        return _outPtsCache;
      }
      else
        return null;
    }




    //draws composite curve
    private void renderPathRing(Petra.Gis.Geometry.PathRing geom)
    {

      preparePathRingPoints(geom);

      //if something is ready to output...
      if (_lastPointIndex > 0)
      {
        if (_outPtsCache.Length > _lastPointIndex + 1)
          Array.Resize<System.Drawing.Point>(ref _outPtsCache, _lastPointIndex + 1);


        _outGraphic.FillPolygon(_outFillBrush, _outPtsCache);
        _outGraphic.DrawPolygon(_outpen, _outPtsCache);
      }



    }



    private System.Drawing.Point[] preparePathRingPoints(PathRing geom)
    {

      _lastPointIndex = 0;

      foreach (GeometryBase g in geom)
      {
        if (g is LineString)
          addPointsToOpenGraphicsPath(((LineString)g).coordList.getBuffer(), ((LineString)g).coordList.position, ((LineString)g).coordList.Count, ((LineString)g).coordList.dimension);
        else if (g is Arc)
        {
          //stroke arc first
          LineString l = ArcMath.getStrokedArc((Arc)g);
          addPointsToOpenGraphicsPath(l.coordList.getBuffer(), l.coordList.position, l.coordList.Count, l.coordList.dimension);
        }

      }

      //if something is ready to output...
      if (_lastPointIndex > 0)
      {
        if (_outPtsCache.Length > _lastPointIndex + 1)
          Array.Resize<System.Drawing.Point>(ref _outPtsCache, _lastPointIndex + 1);

        return _outPtsCache;
      }
      else
        return null;
    
    }




    private void renderPolygon(Polygon geom)
    {
      GraphicsPath gp = new GraphicsPath();

      foreach (GeometryBase g in geom)
      {
        //_lastPointIndex = 0;
        if (g is LineRing)
          gp.AddPolygon(prepareLineRing((LineRing)g));
        else if (g is PathRing)
          gp.AddPolygon(preparePathRingPoints((PathRing)g));

        gp.AddPolygon(_outPtsCache);
      }

      _outGraphic.FillPath(_outFillBrush, gp);
      _outGraphic.DrawPath(_outpen, gp);
    }




    //multiline
    private void drawMultiArea(MultiLine geom)
    {
      foreach (GeometryBase g in geom)
        RenderGeometry(g);

    }




    //geometrycollection
    private void drawMultiArea(GeometryCollection geom)
    {
      foreach (GeometryBase g in geom)
        RenderGeometry(g);
    }



    /// <summary>
    /// process 3 operations in one step:
    /// change geometry spatial coordinates to screen coordinates
    /// remove duplicate verticies
    /// Sutherland-Hodgman polygon clip updated to single pass
    /// </summary>
    /// <param name="buffer">byte[] with geometry coordinates</param>
    /// <param name="start">start index in buffer</param>
    /// <param name="numPoints">number of points in array</param>
    /// <param name="dim">dimension of geometry (2d, 3d)</param>
    private void addPointsToOpenGraphicsPath(byte[] buffer, int start, int numPoints, Dimension dim)
    {
      int numOfEdges = numPoints - 1;

      System.Drawing.Point startPt = new System.Drawing.Point();
      System.Drawing.Point endPt = new System.Drawing.Point();
      //System.Drawing.Point[] _outPtsCache = new System.Drawing.Point[numPoints];
      System.Drawing.Point[] retPts;
      PointD gisPt = new PointD();

      //int lastPtPtr = -1;

      //if necessary resize array
      if(_lastPointIndex + numPoints > _outPtsCache.Length)
        Array.Resize<System.Drawing.Point>(ref _outPtsCache, _lastPointIndex + numPoints);

      MemoryStream m = new MemoryStream(buffer);
      m.Position = start;
      BinaryReader b = new BinaryReader(m);

      //get first point ordinates
      gisPt.X = b.ReadDouble();
      gisPt.Y = b.ReadDouble();
      if (dim == Dimension.dim3D)
        b.ReadDouble();

      //transform start point
      startPt = _transform.TransformPoint(gisPt);

      for (int i = 0; i < numOfEdges; i++)
      { 
        //get next point ordinates
        gisPt.X = b.ReadDouble();
        gisPt.Y = b.ReadDouble();
        if (dim == Dimension.dim3D)
          b.ReadDouble();

        //transform next point
        endPt = _transform.TransformPoint(gisPt);

        //   points not equal //and (both points are not in same zone or are inside bbox)
        if ((startPt != endPt) )//&& ((ims & ime) != ime || (ims | ime) == 0))
        {

          retPts = _map.Extent.lineIntersect(startPt, endPt);

          if (retPts != null)
          {
            foreach (System.Drawing.Point p in retPts)
            {
              if (_lastPointIndex == 0) //first point cant be drawn, just save it
              {
                _outPtsCache[_lastPointIndex++] = p;
                //lastPtPtr = 0;
              }
              else
                if (p != _outPtsCache[_lastPointIndex]) //eliminate duplicate points after clipping again
                {
                  _outPtsCache[_lastPointIndex++] = p;
                }
            }
          }
        }
        startPt = endPt;
      }

      //redim _outPtsCache by last point
      //if (lastPtPtr > 0)
      //{
      //  Array.Resize<System.Drawing.Point>(ref _outPtsCache, lastPtPtr + 1);
      //  //add points to path
      //  //_gdiPluspath.AddLines(_outPtsCache);
      //  //renderer path ??
      //}
    }





    //private void drawOpenGraphicsPath(PointF[] points, int start, int numPoints)
    //{
    //  System.Drawing.Point startPt = new System.Drawing.Point();
    //  System.Drawing.Point endPt = new System.Drawing.Point();
    //  //System.Drawing.Point[] _outPtsCache = new System.Drawing.Point[numPoints - start];
    //  System.Drawing.Point[] retPts;
    //  int lastPtPtr = -1;

    //  int numOfEdges = numPoints - start - 1;

    //  Array.Resize<System.Drawing.Point>(ref _outPtsCache, numPoints);

    //  //transform start point
    //  startPt = _transfMatrix.TransformPoint(points[start]);


    //  for (int i = start + 1; i < numOfEdges; i++)
    //  {

    //    endPt = _transfMatrix.TransformPoint(points[i]);

    //    retPts = this.map.extent.lineIntersect(startPt, endPt);

    //    if (retPts != null)
    //    {
    //      foreach (System.Drawing.Point p in retPts)
    //      {
    //        if (lastPtPtr == -1) //first point
    //        {
    //          _outPtsCache[0] = p;
    //          lastPtPtr = 0;
    //        }
    //        else
    //          if (p != _outPtsCache[lastPtPtr]) //eliminate duplicate points after clipping again
    //          {
    //            _outPtsCache[lastPtPtr++] = p;
    //          }
    //      }
    //    }
    //    startPt = endPt;
    //  }
    //  //redim _outPtsCache by last point
    //  if (lastPtPtr > 0)
    //  {
    //    Array.Resize<System.Drawing.Point>(ref _outPtsCache, lastPtPtr + 1);
    //    //add points to path
    //    _gdiPluspath.AddLines(_outPtsCache);
    //    //renderer path ??
    //  }
    //}

  }
}
